let bluebird = require("bluebird");
let r = require("redis");

const { BusiError, SysError } = require('../exception/exception');

bluebird.promisifyAll(r);

function retry(options) {
    if (options.error && options.error.code === 'ECONNREFUSED') {
        // End reconnecting on a specific error and flush all commands with
        // a individual error
        return new Error('The server refused the connection');
    }
    if (options.total_retry_time > 1000 * 60 * 60) {
        // End reconnecting after a specific timeout and flush all commands
        // with a individual error
        return new Error('Retry time exhausted');
    }
    // if (options.attempt > 10) {
    //     // End reconnecting with built in error
    //     return undefined;
    // }
    console.logw("redis retry......", options.error);

    // reconnect after
    return Math.min(options.attempt * 100, 3000);
}

class Redis {
    constructor() {
        this.__initFlag__ = false;
    }

    /**
     * 
     * @param {{password: String, db: Number, port: Number, host: String}} redisConfig 
     */
    init(redisConfig) {
        const option = {
            retry_strategy: retry
        };

        if (redisConfig["redis.password"]) {
            option.password = redisConfig["redis.password"];
        }

        if (redisConfig["redis.db"]) {
            option.db = redisConfig["redis.db"];
        }

        this.client = r.createClient(redisConfig["redis.port"], redisConfig["redis.host"], option);

        this.__initFlag__ = true;
    }

    getClient() {
        if (!this.__initFlag__) {
            throw new BusiError(500, "redis未初始化， 请调用init()方法初始化redis");
        }

        return this.client;
    }

    on(event, func) {
        this.getClient().on(event, func);
    }

    async select(index) {
        let res = await this.getClient().selectAsync(index);
        return res;
    }

    async setMapByName(name, setObj) {
        if (typeof setObj !== 'object')
            return;
        for (let k in setObj) {
            if (setObj[k] == null) {
                setObj[k] = "";
            } else {
                setObj[k] = setObj[k].toString();
            }
        }
        await this.getClient.hmsetAsync(name, setObj);
    }


    async getHashTable(mapName) {
        return await this.getClient().hgetallAsync(mapName);
    }

    async setHashItem(mapName, itemName, value) {
        //console.logg("setHashItem",mapName,itemName,value)
        return await this.getClient().hsetAsync(mapName, itemName, value);

    }

    /**
     *
     * @param key
     * @param score
     * @param val
     * @returns {Promise<any>}
     */
    zadd(key, score, val) {
        return new Promise((resolve, reject) => {
            this.getClient().zadd(key, score, val.toString(), (error, result) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    sortSetadd(key, score, val) {
        return new Promise((resolve, reject) => {
            this.getClient().zadd(key, score, val.toString(), (error, result) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    }
    sortSetDel(key, val) {
        return new Promise((resolve, reject) => {
            this.getClient().zrem(key, val.toString(), (error, result) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    sortSetGetList(key,  cnt) {
        return new Promise((resolve, reject) => {
            this.getClient().zrangeByScore(key, cnt, (error, result) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    /**
     *
     * @param key
     * @returns {Promise<any>}
     */
    del(key) {
        return new Promise((resolve, reject) => {
            this.getClient().del(key, (error, result) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    /**
     *
     * @param {String} key
     * @param {Object} val
     * @param {Number?} expireTime
     * @returns {Promise<any>}
     */
    set(key, val, expireTime) {
        return new Promise((resolve, reject) => {
            if (typeof val === 'object') {
                val = JSON.stringify(val);
            }

            let args = [key, val];
            if (expireTime) {
                args.push("EX");
                args.push(expireTime);
            }
            this.getClient().set(args, (error, result) => {
                if (error) {
                    console.log("set key:", key, val, "err:", error);
                    reject('error:' + error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    incrby(key, val) {
        return new Promise((resolve, reject) => {
            val = Number(val);
            let args = [key, val];
            this.getClient().incrby(args, (error, result) => {
                if (error) {
                    console.log("incrby key:", key, val, "err:", error);
                    reject('error:' + error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    /**
     *
     * @param {String} key
     * @param val
     * @param {Number} expireTime
     * @returns {Promise<*>}
     */
    async lock(key, val, expireTime) {
        return await this.setNx(key, val, expireTime);
    }

    /**
     *
     * @param key
     * @param val
     * @param expireTime
     * @returns {Promise<any>}
     */
    setNx(key, val, expireTime) {
        return new Promise((resolve, reject) => {
            if (typeof val === 'object') {
                val = JSON.stringify(val);
            }

            let args = [key, val];
            if (expireTime) {
                args.push("EX");
                args.push(expireTime);
            }
            args.push("NX");
            this.getClient().set(args, (error, result) => {
                if (error) {
                    console.logw("set key nx:", key, val, "err:", error);
                    reject('error:' + error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    /**
     * 当该键存在时才设置
     * @param key
     * @param val
     * @param expireTime
     * @returns {Promise<any>}
     */
    setXx(key, val, expireTime) {
        return new Promise((resolve, reject) => {
            if (typeof val === 'object') {
                val = JSON.stringify(val);
            }

            let args = [key, val];
            if (expireTime) {
                args.push("EX");
                args.push(expireTime);
            }
            args.push("XX");
            this.getClient().set(args, (error, result) => {
                if (error) {
                    console.logw("set key xx:", key, val, "err:", error);
                    reject('error:' + error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    /**
     *
     * @param key
     * @returns {Promise<any>}
     */
    get(key) {
        return new Promise((resolve, reject) => {
            this.getClient().get([key], (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    try {
                        let obj = JSON.parse(result);
                        if (obj) {
                            result = obj;
                        }
                    } catch (e) {
                        // console.log("json parse result err:", e)
                    }

                    resolve(result);
                }
            });
        });
    }

    hget(key, hkey) {
        return new Promise((resolve, reject) => {
            this.getClient().hget(key, hkey, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    if (typeof result === "string") {
                        let res = result;
                        try {
                            res = JSON.parse(result);
                        } catch (e) {
                            console.log("hget parse err:", e);
                        }
                        result = res;
                    }
                    resolve(result);
                }
            });
        });
    }

    hvals(key) {
        return new Promise((resolve, reject) => {
            this.getClient().hvals(key, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    if (typeof result === "string") {
                        let res = result;
                        try {
                            res = JSON.parse(result);
                        } catch (e) {
                            console.log("hget parse err:", e);
                        }
                        result = res;
                    }
                    resolve(result);
                }
            });
        });
    }

    hdel(key, hkey) {
        return new Promise((resolve, reject) => {
            this.getClient().hdel(key, hkey, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    if (typeof result === "string") {
                        let res = result;
                        try {
                            res = JSON.parse(result);
                        } catch (e) {
                            console.log("hdel parse err:", e);
                        }
                        result = res;
                    }
                    resolve(result);
                }
            });
        });
    }

    hscan(key, count) {
        let args = [key, 0, "COUNT", count];
        return new Promise((resolve, reject) => {
            this.getClient().hscan(args, (error, result) => {
                if (error) {
                    console.log("hscan key err:", key, "err:", error);
                    reject('error:' + error);
                } else {
                    let list = [];
                    for (let i = 0; i < result[1].length; i++) {
                        let item = result[1][i];
                        if (typeof item === "string") {
                            try {
                                item = JSON.parse(item);
                                if (typeof item === "object") {
                                    list.push(item);
                                }
                            } catch (e) {

                            }
                        }
                    }

                    list.sort((a, b) => {
                        return parseInt(a.roomId) - parseInt(b.roomId);
                    });
                    resolve(list);
                }
            });
        });
    }

    hset(key, hkey, val) {
        return new Promise((resolve, reject) => {
            if (typeof val === "object") {
                val = JSON.stringify(val);
            }

            this.getClient().hset(key, hkey, val, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    for (let key in result) {
                        if (typeof result[key] === "string") {
                            let res = result[key];
                            try {
                                res = JSON.parse(result[key]);
                            } catch (e) {
                                // console.log("hset parse err:", e);
                            }
                            result[key] = res;
                        }
                    }
                    resolve(result);
                }
            });
        });
    }

    /**
     * LTRIM key start stop
     * @param key
     * @param start
     * @param stop
     * @returns {Promise<any>}
     */
    ltrim(key, start, stop) {
        return new Promise((resolve, reject) => {
            this.getClient().ltrim(key, start, stop, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    /**
     * LPUSH key value [value ...]
     * @param {String} key
     * @param {any} val
     * @returns {Promise<any>}
     */
    lpush(key, val) {
        return new Promise((resolve, reject) => {
            if (typeof val === "object") {
                val = JSON.stringify(val);
            }

            this.getClient().lpush(key, val, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    resolve(result);
                }
            });
        });
    }

    /**
     * LRANGE key start stop
     * @param {String} key
     * @param start
     * @param stop
     * @returns {Promise<any>}
     */
    lrange(key, start, stop) {
        return new Promise((resolve, reject) => {
            this.getClient().lrange(key, start, stop, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    for (let key in result) {
                        if (typeof result[key] === "string") {
                            let res = result[key];
                            try {
                                res = JSON.parse(result[key]);
                            } catch (e) {
                                // console.log("lpush parse err:", e);
                            }
                            result[key] = res;
                        }
                    }
                    resolve(result);
                }
            });
        });
    }

    hgetall(key, hkey) {
        return new Promise((resolve, reject) => {
            this.getClient().hgetall(key, (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    for (let key in result) {
                        if (typeof result[key] === "string") {
                            let res = result[key];
                            try {
                                res = JSON.parse(result[key]);
                            } catch (e) {
                                console.log("hgetall parse err:", e);
                            }
                            result[key] = res;
                        }
                    }
                    resolve(result);
                }
            });
        });
    }

    zrangeByScore(key, limit) {
        return new Promise((resolve, reject) => {
            this.getClient().zrangebyscore([key, "-inf", "+inf", 'LIMIT', 0, limit], (error, result) => {
                if (error) {
                    reject('error:' + error);
                } else {
                    for (let i = 0; i < result.length; i++) {
                        result[i] = JSON.parse(result[i]);
                    }
                    resolve(result);
                }
            });
        });
    }

    zrmByScore(key, min, max) {
        return new Promise((resolve, reject) => {
            this.getClient().zremrangebyscore([key, min, max], (err, res) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(res);
                }
            });
        });
    }

    async getHashItem(mapName, itemName) {
        let ret = await this.getClient().hgetAsync(mapName, itemName);
        return ret;
    }

    async delHashTable(name) {
        return await this.getClient().delAsync(name);
    }

    async delHashItem(mapName, itemName) {
        return await this.getClient().hdelAsync(mapName, itemName);
    }

    async getString(key) {
        let ret = await this.getClient().getAsync(key);
        return ret;
    }

    async scanMap(name, index, count) {
        let ret = await this.getClient().send_commandAsync('hscan', [name, index, 'count', count]);
        return ret;
    }

    async scanRaw(index, count) {
        let ret = await this.getClient().send_commandAsync('scan', [index, 'count', count]);
        return ret;
    }

    async setString(key, value) {
        let ret = await this.getClient().setAsync(key, value);
        return ret;
    }

    async getListLen(name) {
        let len = await this.getClient().llenAsync(name);
        return Number(len);
    }

    async getList(key, start, end) {
        let ret = await this.getClient().lrangeAsync(key, start, end);
        return ret;
    }

    async keepListIn(key, start, end) {
        await this.getClient().ltrimAsync(key, start, end);
    }

    async lpopList(name) {
        let ret = await this.getClient().lpopAsync(name);
        return ret;
    }

    async rpushList(name, value) {
        let ret = await this.getClient().rpushAsync(name, value);
        return ret;
    }

    async lpushList(name, value) {
        let ret = await this.getClient().lpushAsync(name, value);
        return ret;
    }

    //LSET guestList 1 user11
    async setListItem(name, index, value) {
        let ret = await this.getClient().lsetAsync(name, index, value);
        return ret;
    }

    async getValuebyKey(mapName, key) {
        let ret = await this.getClient().hgetAsync(mapName, key);
        return ret;
    }

    //存在很大的性能隐患，当一个map的键值很多的时候，会造成阻塞,所以设计的时候不要超过100个key
    async getMapbyName(mapName) {
        let ret = await this.getClient().hgetall(mapName);
        return ret;
    }

    //操作redis数据，减扣玩家房卡
    async decreaseUserCard(uid, deNum) {
        if (deNum === undefined) deNum = 0;
        let data = await this.getHashItem('zjh_usersMap', String(uid));
        if (data === 'undefined' || data == null || data === undefined) {
            console.log('get userMap failed', data);
            return null;
        }
        let obj = JSON.parse(data);
        let n = Number(deNum);
        obj.cardNum = Number(obj.cardNum);
        obj.cardNum -= n;
        if (obj.cardNum < 0) obj.cardNum = 0;
        data = JSON.stringify(obj);
        await this.setHashItem('zjh_usersMap', String(uid), data);
        return obj;
    }

    //
    async delUserFriend(uid, friendUid) {
        let n1 = friendUid, n2 = uid;
        let unionId = n1 + '_' + n2;
        if (n1 > n2) unionId = n2 + '_' + n1;
        //await this.delHashItem('friendMap',unionId);
        await this.setHashItem('friendMap', unionId, 0);
    }


    async ssetAdd(setName, score, str) {
        let ret = await this.getClient().send_commandAsync('zadd', [setName, score, str]);
        return ret;
    }

    async ttl(key) {
        let ret = await this.getClient().send_commandAsync('ttl', [key]);
        return ret;
    }

    async ssetGetPage(setName, page) {
        let start = page * 10;
        let end = start + 9;
        //ZREVRANGE salary 0 -1 WITHSCORES
        let ret = await this.getClient().send_commandAsync('zrevrange', [setName, start, end]);
        return ret;
    }


    async ssetKeepInRanage(setName, max) {
        let ret = await this.getClient().send_commandAsync('zremrangebyrank', [setName, max, -1]);
        return ret;
    }

    async setExpire(key, second) {
        let ret = await this.getClient().send_commandAsync('expire', [key, second]);
        return ret;
    }


    async delString(key) {
        let ret = await this.getClient().delAsync(key);
        return ret;
    }

    async publishUnion(member){
        let ret = await this.getClient().publish("SUB_MESSAGE_CHAN", member);
        return ret;
    }
}

let redis = new Redis();
module.exports = redis;